﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Lilium.Control
{
    public static class TypeCast
    {
        public static Func<TypeCastContext, TResult> Cast<TResult>(this object value)
        {
            return context =>
            {
                if (value is TResult)
                {
                    return (TResult)value;
                }
                else
                {
                    context.InvalidCast(typeof(TResult), value);
                    return default(TResult);
                }
            };
        }

        public static Func<TypeCastContext, TResult> Or<TResult>(this Func<TypeCastContext, TResult> first, Func<TypeCastContext, TResult> second)
        {
            return context =>
            {
                var result = first(context);
                if (!context.Failed)
                    return result;
                else
                {
                    context.Restore();
                    return second(context);
                }
            };
        }

        public static TResult Run<TResult>(this Func<TypeCastContext, TResult> func)
        {
            var context = new TypeCastContext();
            var result = func(context);

            if (context.Failed)
                throw new TypeCastException(); // ...

            return result;
        }

        public static Func<TypeCastContext, TResult2> Select<TResult1, TResult2>(
            this Func<TypeCastContext, TResult1> func, 
            Func<TResult1, TResult2> project)
        {
            return context =>
            {
                var result = func(context);

                if (!context.Failed)
                {
                    return project(result);
                }
                else
                {
                    return default(TResult2);
                }
            };
        }

        public static Func<TypeCastContext, TResult3> SelectMany<TResult1, TResult2, TResult3>(
            this Func<TypeCastContext, TResult1> func,
            Func<TResult1, Func<TypeCastContext, TResult2>> selector,
            Func<TResult1, TResult2, TResult3> projector)
        {
            return context =>
            {
                var result1 = func(context);
                if (!context.Failed)
                {
                    var continuation = selector(result1);
                    var result2 = continuation(context);
                    if (!context.Failed)
                    {
                        return projector(result1, result2);
                    }
                }

                return default(TResult3);
            };
        }

        public static Func<TypeCastContext, TResult> Where<TResult>(this Func<TypeCastContext, TResult> fun, Func<TResult, bool> predicate)
        {
            return context =>
            {
                var result = fun(context);
                if (!context.Failed)
                {
                    if (predicate(result))
                    {
                        return result;
                    }
                    else
                    {
                        context.PredicateFailed();
                        return default(TResult);
                    }
                }
                else
                    return result;
            };
        }
    }

    public class TypeCastContext
    {
        public TypeCastContext()
        {
        }

        public void InvalidCast(Type type, object target)
        {
            Failed = true;
        }

        public void PredicateFailed()
        {
            Failed = true;
        }

        public void Restore()
        {
            Failed = false;
        }

        #region public bool Failed

        private bool m_Failed;

        public bool Failed
        {
            get
            {
                return m_Failed;
            }
            private set
            {
                m_Failed = value;
            }
        }

        #endregion
    }

    [Serializable]
    public class TypeCastException : Exception
    {
        public TypeCastException() { }
        public TypeCastException(string message) : base(message) { }
        public TypeCastException(string message, Exception inner) : base(message, inner) { }
        protected TypeCastException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context) { }
    }
}